{*****************************************************************************}
{                                                                             }
{    Tnt Delphi Unicode Controls                                              }
{                                                                             }
{    Portions created by Wild Hunter are                                      }
{    Copyright (c) 2003 Wild Hunter (raguotis@freemail.lt)                    }
{                                                                             }
{    Portions created by Stanley Xu are                                       }
{    Copyright (c) 1999-2006 Stanley Xu (http://getgosurf.com/)               }
{    http://getgosurf.com/?go=support&sg=feedback&ln=en                       }
{                                                                             }
{    Portions created by Borland Software Corporation are                     }
{    Copyright (c) 1995-2001 Borland Software Corporation                     }
{                                                                             }
{*****************************************************************************}

unit TntIniFiles;

{$R-,T-,H+,X+}
{$INCLUDE TntCompilers.inc}

interface

uses
  Classes, IniFiles,
  TntClasses;

type

  TTntCustomIniFile = class({TCustomIniFile}TObject{TNT-ALLOW TObject})
  private
    FFileName: WideString;
  public
    constructor Create(const FileName: WideString);
    function SectionExists(const Section: WideString): Boolean;
    function ReadString(const Section, Ident, Default: WideString): WideString; virtual; abstract;
    procedure WriteString(const Section, Ident, Value: WideString); virtual; abstract;
    function ReadInteger(const Section, Ident: WideString; Default: Longint): Longint; virtual;
    procedure WriteInteger(const Section, Ident: WideString; Value: Longint); virtual;
    function ReadBool(const Section, Ident: WideString; Default: Boolean): Boolean; virtual;
    procedure WriteBool(const Section, Ident: WideString; Value: Boolean); virtual;
    function ReadBinaryStream(const Section, Name: WideString; Value: TStream): Integer; virtual;
    function ReadDate(const Section, Name: WideString; Default: TDateTime): TDateTime; virtual;
    function ReadDateTime(const Section, Name: WideString; Default: TDateTime): TDateTime; virtual;
    function ReadFloat(const Section, Name: WideString; Default: Double): Double; virtual;
    function ReadTime(const Section, Name: WideString; Default: TDateTime): TDateTime; virtual;
    procedure WriteBinaryStream(const Section, Name: WideString; Value: TStream); virtual;
    procedure WriteDate(const Section, Name: WideString; Value: TDateTime); virtual;
    procedure WriteDateTime(const Section, Name: WideString; Value: TDateTime); virtual;
    procedure WriteFloat(const Section, Name: WideString; Value: Double); virtual;
    procedure WriteTime(const Section, Name: WideString; Value: TDateTime); virtual;
    procedure ReadSection(const Section: WideString; Strings: TTntStrings); virtual; abstract;
    procedure ReadSections(Strings: TTntStrings); virtual; abstract;
    procedure ReadSectionValues(const Section: WideString; Strings: TTntStrings); virtual; abstract;
    procedure EraseSection(const Section: WideString); virtual; abstract;
    procedure DeleteKey(const Section, Ident: WideString); virtual; abstract;
    procedure UpdateFile; virtual; abstract;
    function ValueExists(const Section, Ident: WideString): Boolean;
    property FileName: WideString read FFileName;
  end;

  { TTntStringHash - used internally by TTntMemIniFile to optimize searches. }

  PPTntHashItem = ^PTntHashItem;
  PTntHashItem = ^TTntHashItem;
  TTntHashItem = record
    Next: PTntHashItem;
    Key: WideString;
    Value: Integer;
  end;

  TTntStringHash = class
  private
    Buckets: array of PTntHashItem;
  protected
    function Find(const Key: WideString): PPTntHashItem;
    function HashOf(const Key: WideString): Cardinal; virtual;
  public
    constructor Create(Size: Integer = 256);
    destructor Destroy; override;
    procedure Add(const Key: WideString; Value: Integer);
    procedure Clear;
    procedure Remove(const Key: WideString);
    function Modify(const Key: WideString; Value: Integer): Boolean;
    function ValueOf(const Key: WideString): Integer;
  end;

  { TTntHashedStringList - A TTntStringList that uses TTntStringHash to improve the
    speed of Find }

  TTntHashedStringList = class(TTntStringList)
  private
    FValueHash: TTntStringHash;
    FNameHash: TTntStringHash;
    FValueHashValid: Boolean;
    FNameHashValid: Boolean;
    procedure UpdateValueHash;
    procedure UpdateNameHash;
  protected
    procedure Changed; override;
  public
    destructor Destroy; override;
    function IndexOf(const S: WideString): Integer; override;
    function IndexOfName(const Name: WideString): Integer; override;
  end;

  { TTntMemIniFile - loads and entire ini file into memory and allows all
    operations to be performed on the memory image.  The image can then
    be written out to the disk file }

  TTntMemIniFile = class(TTntCustomIniFile)
  private
    FSections: TTntStringList;
    function AddSection(const Section: WideString): TTntStrings;
    function GetCaseSensitive: Boolean;
    procedure LoadValues; {$IFDEF DONOT_OVERWRITE_OTHER_SECTIONS} virtual;
    procedure AfterLoadFromFile; virtual; abstract; {$ENDIF}
    procedure SetCaseSensitive(Value: Boolean);
  public
    constructor Create(const FileName: WideString); virtual;
    destructor Destroy; override;
    procedure Clear;
    procedure DeleteKey(const Section, Ident: WideString); override;
    procedure EraseSection(const Section: WideString); override;
    procedure GetStrings(List: TTntStrings);
    procedure ReadSection(const Section: WideString; Strings: TTntStrings); override;
    procedure ReadSections(Strings: TTntStrings); override;
    procedure ReadSectionValues(const Section: WideString; Strings: TTntStrings); override;
    function ReadString(const Section, Ident, Default: WideString): WideString; override;
    procedure Rename(const FileName: WideString; Reload: Boolean);
    procedure SetStrings(List: TTntStrings);
    procedure UpdateFile; override;
    procedure WriteString(const Section, Ident, Value: WideString); override;
    property CaseSensitive: Boolean read GetCaseSensitive write SetCaseSensitive;
  end;

{$IFDEF DONOT_OVERWRITE_OTHER_SECTIONS}

  {  o  TTntMemIniFile buffers all changes to the INI file in memory. To write the data from
        memory back to the associated INI file, call the UpdateFile() method.

        However, the whole content of this INI file will be overwritten. Even those sections
        that are not used. This is dangerous, if two instances try to change the same file
        at the same time, without some method of managing access the instances may well end
        up overwriting each other's work.

     o  TTntMemIniFileEx implementes a simple idea: To check the timestamp before each operation.
        If the file is modified, TTntMemIniFileEx will reload the file to keep the content
        updated.

     o  CONCLUSION:

         #  TTntMemIniFileEx and TTntMemIniFile are ideal for read-only access. For
            instance: To read large language files, etc.

         #  To perform mass WriteString() operations, please use the following code.

              BeginUpdate();
              try
                for I := 0 to 10000 do
                  WriteString(...);
              finally;
                EndUpdate();
                UpdateFile;
              end;

         #  IF YOU HAVE SOME BETTER IDEAS, PLEASE FEEL FREE TO SHARE THEM WITH US.
  }

  TTntMemIniFileEx = class(TTntMemIniFile)
  private
    FUpdateCount: Integer;
    FModified: Boolean;
    FLastAccessed: Integer;
    function FileRealLastAccessedTime: Integer;
    procedure GetLatestVersion;
    procedure AfterLoadFromFile; override;
  public
    constructor Create(const FileName: WideString); override;
    procedure BeginUpdate; virtual;
    procedure EndUpdate; virtual;
    function ReadString(const Section, Ident, Default: WideString): WideString; override;
    procedure WriteString(const Section, Ident, Value: WideString); override;
    procedure ReadSection(const Section: WideString; Strings: TTntStrings); override;
    procedure ReadSections(Strings: TTntStrings); override;
    procedure ReadSectionValues(const Section: WideString; Strings: TTntStrings); override;
    procedure DeleteKey(const Section, Ident: WideString); override;
    procedure EraseSection(const Section: WideString); override;
    procedure UpdateFile; override;
  end;
{$ENDIF}

{$IFDEF MSWINDOWS}
  { TTntIniFile - Encapsulates the Windows INI file interface
    (Get/SetPrivateProfileXXX functions) }

  TTntIniFile = class(TTntCustomIniFile)
  private
    FAnsiIniFile: TIniFile; // For compatibility with Windows 95/98/Me
    procedure WideCheckFileExists;
  public
    constructor Create(const FileName: WideString);
    destructor Destroy; override;
    function ReadString(const Section, Ident, Default: WideString): WideString; override;
    procedure WriteString(const Section, Ident, Value: WideString); override;
    procedure ReadSection(const Section: WideString; Strings: TTntStrings); override;
    procedure ReadSections(Strings: TTntStrings); override;
    procedure ReadSectionValues(const Section: WideString; Strings: TTntStrings); override;
    procedure EraseSection(const Section: WideString); override;
    procedure DeleteKey(const Section, Ident: WideString); override;
    procedure UpdateFile; override;
  end;
{$ELSE}
  TTntIniFile = class(TTntMemIniFile)
  public
    destructor Destroy; override;
  end;
{$ENDIF}


implementation

uses
  RTLConsts, SysUtils, TntSystem, TntSysUtils, TntWindows
{$IFDEF COMPILER_9_UP} , WideStrUtils {$ELSE} , TntWideStrUtils {$ENDIF}
{$IFDEF MSWINDOWS}     , Windows      {$ENDIF};

{ TTntCustomIniFile }

constructor TTntCustomIniFile.Create(const FileName: WideString);
begin
  FFileName := FileName;
end;

function TTntCustomIniFile.SectionExists(const Section: WideString): Boolean;
var
  S: TTntStrings;
begin
  S := TTntStringList.Create;
  try
    ReadSection(Section, S);
    Result := S.Count > 0;
  finally
    S.Free;
  end;
end;

function TTntCustomIniFile.ReadInteger(const Section, Ident: WideString;
  Default: Longint): Longint;
var
  IntStr: WideString;
begin
  IntStr := ReadString(Section, Ident, '');
  if (Length(IntStr) > 2) and (IntStr[1] = WideChar('0')) and
    ((IntStr[2] = WideChar('X')) or (IntStr[2] = WideChar('x'))) then
    IntStr := WideString('$') + Copy(IntStr, 3, Maxint);
  Result := StrToIntDef(IntStr, Default);
end;

procedure TTntCustomIniFile.WriteInteger(const Section, Ident: WideString; Value: Longint);
begin
  WriteString(Section, Ident, IntToStr(Value));
end;

function TTntCustomIniFile.ReadBool(const Section, Ident: WideString;
  Default: Boolean): Boolean;
begin
  Result := ReadInteger(Section, Ident, Ord(Default)) <> 0;
end;

function TTntCustomIniFile.ReadDate(const Section, Name: WideString; Default: TDateTime): TDateTime;
var
  DateStr: WideString;
begin
  DateStr := ReadString(Section, Name, '');
  Result := Default;
  if DateStr <> '' then
  try
    Result := StrToDate(DateStr);
  except
    on EConvertError do
    else raise;
  end;
end;

function TTntCustomIniFile.ReadDateTime(const Section, Name: WideString; Default: TDateTime): TDateTime;
var
  DateStr: WideString;
begin
  DateStr := ReadString(Section, Name, '');
  Result := Default;
  if DateStr <> '' then
  try
    Result := StrToDateTime(DateStr);
  except
    on EConvertError do
    else raise;
  end;
end;

function TTntCustomIniFile.ReadFloat(const Section, Name: WideString; Default: Double): Double;
var
  FloatStr: WideString;
begin
  FloatStr := ReadString(Section, Name, '');
  Result := Default;
  if FloatStr <> '' then
  try
    Result := StrToFloat(FloatStr);
  except
    on EConvertError do
    else raise;
  end;
end;

function TTntCustomIniFile.ReadTime(const Section, Name: WideString; Default: TDateTime): TDateTime;
var
  TimeStr: WideString;
begin
  TimeStr := ReadString(Section, Name, '');
  Result := Default;
  if TimeStr <> '' then
  try
    Result := StrToTime(TimeStr);
  except
    on EConvertError do
    else raise;
  end;
end;

procedure TTntCustomIniFile.WriteDate(const Section, Name: WideString; Value: TDateTime);
begin
  WriteString(Section, Name, DateToStr(Value));
end;

procedure TTntCustomIniFile.WriteDateTime(const Section, Name: WideString; Value: TDateTime);
begin
  WriteString(Section, Name, DateTimeToStr(Value));
end;

procedure TTntCustomIniFile.WriteFloat(const Section, Name: WideString; Value: Double);
begin
  WriteString(Section, Name, FloatToStr(Value));
end;

procedure TTntCustomIniFile.WriteTime(const Section, Name: WideString; Value: TDateTime);
begin
  WriteString(Section, Name, TimeToStr(Value));
end;

procedure TTntCustomIniFile.WriteBool(const Section, Ident: WideString; Value: Boolean);
const
  Values: array[Boolean] of WideString = ('0', '1');
begin
  WriteString(Section, Ident, Values[Value]);
end;

function TTntCustomIniFile.ValueExists(const Section, Ident: WideString): Boolean;
var
  S: TTntStrings;
begin
  S := TTntStringList.Create;
  try
    ReadSection(Section, S);
    Result := S.IndexOf(Ident) > -1;
  finally
    S.Free;
  end;
end;

function TTntCustomIniFile.ReadBinaryStream(const Section, Name: WideString;
  Value: TStream): Integer;
var
  Text: String; // Not Unicode: Due to HexToBin is not Unicode
  Stream: TMemoryStream;
  Pos: Integer;
begin
  Text := ReadString(Section, Name, '');
  if Text <> '' then
  begin
    if Value is TMemoryStream then
      Stream := TMemoryStream(Value)
    else Stream := TMemoryStream.Create;
    try
      Pos := Stream.Position;
      Stream.SetSize(Stream.Size + Length(Text) div 2);
      HexToBin(PChar(Text), PChar(Integer(Stream.Memory) + Stream.Position), Length(Text) div 2);
      Stream.Position := Pos;
      if Value <> Stream then Value.CopyFrom(Stream, Length(Text) div 2);
      Result := Stream.Size - Pos;
    finally
      if Value <> Stream then Stream.Free;
    end;
  end else Result := 0;
end;

procedure TTntCustomIniFile.WriteBinaryStream(const Section, Name: WideString;
  Value: TStream);
var
  Text: string; // Not Unicode: Due to BinToHex is not Unicode
  Stream: TMemoryStream;
begin
  SetLength(Text, (Value.Size - Value.Position) * 2);
  if Length(Text) > 0 then
  begin
    if Value is TMemoryStream then
      Stream := TMemoryStream(Value)
    else Stream := TMemoryStream.Create;
    try
      if Stream <> Value then
      begin
        Stream.CopyFrom(Value, Value.Size - Value.Position);
        Stream.Position := 0;
      end;
      BinToHex(PChar(Integer(Stream.Memory) + Stream.Position), PChar(Text),
        Stream.Size - Stream.Position);
    finally
      if Value <> Stream then Stream.Free;
    end;
  end;
  WriteString(Section, Name, Text);
end;

{ TTntStringHash }

procedure TTntStringHash.Add(const Key: WideString; Value: Integer);
var
  Hash: Integer;
  Bucket: PTntHashItem;
begin
  Hash := HashOf(Key) mod Cardinal(Length(Buckets));
  New(Bucket);
  Bucket^.Key := Key;
  Bucket^.Value := Value;
  Bucket^.Next := Buckets[Hash];
  Buckets[Hash] := Bucket;
end;

procedure TTntStringHash.Clear;
var
  I: Integer;
  P, N: PTntHashItem;
begin
  for I := 0 to Length(Buckets) - 1 do
  begin
    P := Buckets[I];
    while P <> nil do
    begin
      N := P^.Next;
      Dispose(P);
      P := N;
    end;
    Buckets[I] := nil;
  end;
end;

constructor TTntStringHash.Create(Size: Integer);
begin
  inherited Create;
  SetLength(Buckets, Size);
end;

destructor TTntStringHash.Destroy;
begin
  Clear;
  inherited;
end;

function TTntStringHash.Find(const Key: WideString): PPTntHashItem;
var
  Hash: Integer;
begin
  Hash := HashOf(Key) mod Cardinal(Length(Buckets));
  Result := @Buckets[Hash];
  while Result^ <> nil do
  begin
    if Result^.Key = Key then
      Exit
    else
      Result := @Result^.Next;
  end;
end;

function TTntStringHash.HashOf(const Key: WideString): Cardinal;
var
  I: Integer;
begin
  Result := 0;
  for I := 1 to Length(Key) do
    Result := ((Result shl 2) or (Result shr (SizeOf(Result) * 8 - 2))) xor
      Ord(Key[I]); // Is it OK for WideChar?
end;

function TTntStringHash.Modify(const Key: WideString; Value: Integer): Boolean;
var
  P: PTntHashItem;
begin
  P := Find(Key)^;
  if P <> nil then
  begin
    Result := True;
    P^.Value := Value;
  end
  else
    Result := False;
end;

procedure TTntStringHash.Remove(const Key: WideString);
var
  P: PTntHashItem;
  Prev: PPTntHashItem;
begin
  Prev := Find(Key);
  P := Prev^;
  if P <> nil then
  begin
    Prev^ := P^.Next;
    Dispose(P);
  end;
end;

function TTntStringHash.ValueOf(const Key: WideString): Integer;
var
  P: PTntHashItem;
begin
  P := Find(Key)^;
  if P <> nil then
    Result := P^.Value else
    Result := -1;
end;

{ TTntHashedStringList }

procedure TTntHashedStringList.Changed;
begin
  inherited;
  FValueHashValid := False;
  FNameHashValid := False;
end;

destructor TTntHashedStringList.Destroy;
begin
  FValueHash.Free;
  FNameHash.Free;
  inherited;
end;

function TTntHashedStringList.IndexOf(const S: WideString): Integer;
begin
  UpdateValueHash;
  if not CaseSensitive then
    Result :=  FValueHash.ValueOf(WideUpperCase(S))
  else
    Result :=  FValueHash.ValueOf(S);
end;

function TTntHashedStringList.IndexOfName(const Name: WideString): Integer;
begin
  UpdateNameHash;
  if not CaseSensitive then
    Result := FNameHash.ValueOf(WideUpperCase(Name))
  else
    Result := FNameHash.ValueOf(Name);
end;

procedure TTntHashedStringList.UpdateNameHash;
var
  I: Integer;
  P: Integer;
  Key: WideString;
begin
  if FNameHashValid then Exit;
  if FNameHash = nil then
    FNameHash := TTntStringHash.Create
  else
    FNameHash.Clear;
  for I := 0 to Count - 1 do
  begin
    Key := Get(I);
    P := Pos(NameValueSeparator, Key);
    if P <> 0 then
    begin
      if not CaseSensitive then
        Key := WideUpperCase(Copy(Key, 1, P - 1))
      else
        Key := Copy(Key, 1, P - 1);
      FNameHash.Add(Key, I);
    end;
  end;
  FNameHashValid := True;
end;

procedure TTntHashedStringList.UpdateValueHash;
var
  I: Integer;
begin
  if FValueHashValid then Exit;
  if FValueHash = nil then
    FValueHash := TTntStringHash.Create
  else
    FValueHash.Clear;
  for I := 0 to Count - 1 do
    if not CaseSensitive then
      FValueHash.Add(WideUpperCase(Self[I]), I)
    else
      FValueHash.Add(Self[I], I);
  FValueHashValid := True;
end;

{ TTntMemIniFile }

constructor TTntMemIniFile.Create(const FileName: WideString);
begin
  inherited Create(FileName);
  FSections := TTntHashedStringList.Create;
  FSections.NameValueSeparator := '=';
{$IFDEF LINUX}
  FSections.CaseSensitive := True;
{$ELSE}
  FSections.CaseSensitive := False;
{$ENDIF}
  LoadValues;
end;

destructor TTntMemIniFile.Destroy;
begin
  if FSections <> nil then Clear;
  FSections.Free;
  inherited;
end;

function TTntMemIniFile.AddSection(const Section: WideString): TTntStrings;
begin
  Result := TTntHashedStringList.Create;
  try
    TTntHashedStringList(Result).CaseSensitive := CaseSensitive;
    FSections.AddObject(Section, Result);
  except
    Result.Free;
    raise;
  end;
end;

procedure TTntMemIniFile.Clear;
var
  I: Integer;
begin
  for I := 0 to FSections.Count - 1 do
    TObject(FSections.Objects[I]).Free;
  FSections.Clear;
end;

procedure TTntMemIniFile.DeleteKey(const Section, Ident: WideString);
var
  I, J: Integer;
  Strings: TTntStrings;
begin
  I := FSections.IndexOf(Section);
  if I >= 0 then
  begin
    Strings := TTntStrings(FSections.Objects[I]);
    J := Strings.IndexOfName(Ident);
    if J >= 0 then Strings.Delete(J);
  end;
end;

procedure TTntMemIniFile.EraseSection(const Section: WideString);
var
  I: Integer;
begin
  I := FSections.IndexOf(Section);
  if I >= 0 then
  begin
    TStrings(FSections.Objects[I]).Free;
    FSections.Delete(I);
  end;
end;

function TTntMemIniFile.GetCaseSensitive: Boolean;
begin
  Result := FSections.CaseSensitive;
end;

procedure TTntMemIniFile.GetStrings(List: TTntStrings);
var
  I, J: Integer;
  Strings: TTntStrings;
begin
  List.BeginUpdate;
  try
    for I := 0 to FSections.Count - 1 do
    begin
      List.Add('[' + FSections[I] + ']');
      Strings := TTntStrings(FSections.Objects[I]);
      for J := 0 to Strings.Count - 1 do
        List.Add(Strings[J]);
      List.Add('');
    end;
  finally
    List.EndUpdate;
  end;
end;

procedure TTntMemIniFile.LoadValues;
var
  List: TTntStringList;
begin
  if (FileName <> '') and WideFileExists(FileName) then
  begin
    List := TTntStringList.Create;
    try
      List.LoadFromFile(FileName);
    {$IFDEF DONOT_OVERWRITE_OTHER_SECTIONS}
      AfterLoadFromFile;
    {$ENDIF}
      SetStrings(List);
    finally
      List.Free;
    end;
  end else
    Clear;
end;

procedure TTntMemIniFile.ReadSection(const Section: WideString;
  Strings: TTntStrings);
var
  I, J: Integer;
  SectionStrings: TTntStrings;
begin
  Strings.BeginUpdate;
  try
    Strings.Clear;
    I := FSections.IndexOf(Section);
    if I >= 0 then
    begin
      SectionStrings := TTntStrings(FSections.Objects[I]);
      for J := 0 to SectionStrings.Count - 1 do
        Strings.Add(SectionStrings.Names[J]);
    end;
  finally
    Strings.EndUpdate;
  end;
end;

procedure TTntMemIniFile.ReadSections(Strings: TTntStrings);
begin
  Strings.Assign(FSections);
end;

procedure TTntMemIniFile.ReadSectionValues(const Section: WideString;
  Strings: TTntStrings);
var
  I: Integer;
begin
  Strings.BeginUpdate;
  try
    Strings.Clear;
    I := FSections.IndexOf(Section);
    if I >= 0 then Strings.Assign(TTntStrings(FSections.Objects[I]));
  finally
    Strings.EndUpdate;
  end;
end;

function TTntMemIniFile.ReadString(const Section, Ident,
  Default: WideString): WideString;
var
  I: Integer;
  Strings: TTntStrings;
begin
  I := FSections.IndexOf(Section);
  if I >= 0 then
  begin
    Strings := TTntStrings(FSections.Objects[I]);
    I := Strings.IndexOfName(Ident);
    if I >= 0 then
    begin
      Result := Copy(Strings[I], Length(Ident) + 2, Maxint);
      Exit;
    end;
  end;
  Result := Default;
end;

procedure TTntMemIniFile.Rename(const FileName: WideString; Reload: Boolean);
begin
  FFileName := FileName;
  if Reload then LoadValues;
end;

procedure TTntMemIniFile.SetCaseSensitive(Value: Boolean);
var
  I: Integer;
begin
  if Value <> FSections.CaseSensitive then
  begin
    FSections.CaseSensitive := Value;
    for I := 0 to FSections.Count - 1 do
      with TTntHashedStringList(FSections.Objects[I]) do
      begin
        CaseSensitive := Value;
        Changed;
      end;
    TTntHashedStringList(FSections).Changed;
  end;
end;

procedure TTntMemIniFile.SetStrings(List: TTntStrings);
var
  I, J: Integer;
  S: WideString;
  Strings: TTntStrings;
begin
  Clear;
  Strings := nil;
  for I := 0 to List.Count - 1 do
  begin
    S := Trim(List[I]);
    if (S <> '') and (S[1] <> ';') then
      if (S[1] = '[') and (S[Length(S)] = ']') then
      begin
        Delete(S, 1, 1);
        SetLength(S, Length(S)-1);
        Strings := AddSection(Trim(S));
      end
      else
        if Strings <> nil then
        begin
          J := Pos(FSections.NameValueSeparator, S);
          if J > 0 then // remove spaces before and after NameValueSeparator
            Strings.Add(Trim(Copy(S, 1, J-1)) + FSections.NameValueSeparator + Trim(Copy(S, J+1, MaxInt)) )
          else
            Strings.Add(S);
        end;
  end;
end;

procedure TTntMemIniFile.UpdateFile;
var
  List: TTntStringList;
begin
  List := TTntStringList.Create;
  try
    GetStrings(List);
    List.SaveToFile(FFileName);
  finally
    List.Free;
  end;
end;

procedure TTntMemIniFile.WriteString(const Section, Ident, Value: WideString);
var
  I: Integer;
  S: WideString;
  Strings: TTntStrings;
begin
  I := FSections.IndexOf(Section);
  if I >= 0 then
    Strings := TTntStrings(FSections.Objects[I]) else
    Strings := AddSection(Section);
  S := Ident + FSections.NameValueSeparator + Value;
  I := Strings.IndexOfName(Ident);
  if I >= 0 then Strings[I] := S else Strings.Add(S);
end;


{$IFDEF DONOT_OVERWRITE_OTHER_SECTIONS}

{ TTntMemIniFileEx }

function TTntMemIniFileEx.FileRealLastAccessedTime: Integer;
var
  H: Integer;   // file handle
begin
  Result := 0;
  H := WideFileOpen(FileName, fmOpenWrite); //fmOpenRead (?)
  if H <> -1 then
  try
    Result := FileGetDate(H);
  finally
    FileClose(H);
  end;
end;

procedure TTntMemIniFileEx.GetLatestVersion;
begin
  if FLastAccessed = FileRealLastAccessedTime then
    Exit;

  LoadValues;
  // FLastAccess will be updated in LoadValues(...)
end;

procedure TTntMemIniFileEx.AfterLoadFromFile;
begin
  FLastAccessed := FileRealLastAccessedTime;
  FModified := False;
end;

constructor TTntMemIniFileEx.Create(const FileName: WideString);
begin
  inherited Create(FileName);
  FUpdateCount := 0;
end;

procedure TTntMemIniFileEx.BeginUpdate;
begin
  Inc(FUpdateCount);
end;

procedure TTntMemIniFileEx.EndUpdate;
begin
  Dec(FUpdateCount);
end;

function TTntMemIniFileEx.ReadString(const Section, Ident, Default: WideString): WideString;
begin
  GetLatestVersion;
  Result := inherited ReadString(Section, Ident, Default);
end;

procedure TTntMemIniFileEx.WriteString(const Section, Ident, Value: WideString);
begin
  GetLatestVersion;
  inherited WriteString(Section, Ident, Value);
  FModified := True;
  UpdateFile; // Flush changes to disk
end;

procedure TTntMemIniFileEx.ReadSection(const Section: WideString; Strings: TTntStrings);
begin
  GetLatestVersion;
  inherited ReadSection(Section, Strings);
end;

procedure TTntMemIniFileEx.ReadSections(Strings: TTntStrings);
begin
  GetLatestVersion;
  inherited ReadSections(Strings);
end;

procedure TTntMemIniFileEx.ReadSectionValues(const Section: WideString; Strings: TTntStrings);
begin
  GetLatestVersion;
  inherited ReadSectionValues(Section, Strings);
end;

procedure TTntMemIniFileEx.DeleteKey(const Section, Ident: WideString);
begin
  GetLatestVersion;
  inherited DeleteKey(Section, Ident);
  FModified := True;
  UpdateFile; // Flush changes to disk
end;

procedure TTntMemIniFileEx.EraseSection(const Section: WideString);
begin
  GetLatestVersion;
  inherited EraseSection(Section);
  FModified := True;
  UpdateFile; // Flush changes to disk
end;

procedure TTntMemIniFileEx.UpdateFile;
begin
  if not FModified or (FUpdateCount > 0) then
    Exit;
  inherited;
end;
{$ENDIF}


{$IFDEF MSWINDOWS}
{ TTntIniFile }

constructor TTntIniFile.Create(const FileName: WideString);
begin
  inherited Create(FileName);
  if (not Win32PlatformIsUnicode) then
    FAnsiIniFile := TIniFile.Create(FileName);
end;

destructor TTntIniFile.Destroy;
begin
  UpdateFile; // flush changes to disk
  if (not Win32PlatformIsUnicode) then
    FAnsiIniFile.Free;
  inherited Destroy;
end;

function TTntIniFile.ReadString(const Section, Ident, Default: WideString): WideString;
var
  Buffer: array[0..2047] of WideChar;
begin
  if (not Win32PlatformIsUnicode) then
    { Windows 95/98/Me }
    Result := FAnsiIniFile.ReadString(Section, Ident, Default)
  else begin
    { Windows NT/2000/XP and later }
    GetPrivateProfileStringW(PWideChar(Section),
      PWideChar(Ident), PWideChar(Default), Buffer, Length(Buffer), PWideChar(FFileName));
    Result := WideString(Buffer);
  end;
end;

procedure TTntIniFile.WriteString(const Section, Ident, Value: WideString);
begin
  if (not Win32PlatformIsUnicode) then
    { Windows 95/98/Me }
    FAnsiIniFile.WriteString(Section, Ident, Value)
  else begin
    { Windows NT/2000/XP and later }
    WideCheckFileExists;
    if not WritePrivateProfileStringW(PWideChar(Section), PWideChar(Ident),
      PWideChar(Value), PWideChar(FFileName)) then
      raise EIniFileException.CreateResFmt(@SIniFileWriteError, [FileName]);
  end;
end;

procedure TTntIniFile.ReadSections(Strings: TTntStrings);
const
  BufSize = 16384 * SizeOf(WideChar);
var
  Buffer, P: PWideChar;
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.ReadSections(Strings.AnsiStrings);
  end else
  begin
    { Windows NT/2000/XP and later }
    GetMem(Buffer, BufSize);
    try
      Strings.BeginUpdate;
      try
        Strings.Clear;
        if GetPrivateProfileStringW(nil, nil, nil, Buffer, BufSize,
          PWideChar(FFileName)) <> 0 then
        begin
          P := Buffer;
          while P^ <> WideChar(#0) do
          begin
            Strings.Add(P);
            Inc(P, WStrLen(P) + 1);
          end;
        end;
      finally
        Strings.EndUpdate;
      end;
    finally
      FreeMem(Buffer, BufSize);
    end;
  end; {else}
end;

procedure TTntIniFile.ReadSection(const Section: WideString; Strings: TTntStrings);
const
  BufSize = 16384 * SizeOf(WideChar);
var
  Buffer, P: PWideChar;
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.ReadSection(Section, Strings.AnsiStrings);
  end else
  begin
    { Windows NT/2000/XP and later }
    GetMem(Buffer, BufSize);
    try
      Strings.BeginUpdate;
      try
        Strings.Clear;
        if GetPrivateProfileStringW(PWideChar(Section), nil, nil, Buffer, BufSize,
          PWideChar(FFileName)) <> 0 then
        begin
          P := Buffer;
          while P^ <> #0 do
          begin
            Strings.Add(P);
            Inc(P, WStrLen(P) + 1);
          end;
        end;
      finally
        Strings.EndUpdate;
      end;
    finally
      FreeMem(Buffer, BufSize);
    end;
  end;
end;

procedure TTntIniFile.ReadSectionValues(const Section: WideString; Strings: TTntStrings);
var
  KeyList: TTntStringList;
  I: Integer;
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.ReadSectionValues(Section, Strings.AnsiStrings);
  end else
  begin
    { Windows NT/2000/XP and later }
    KeyList := TTntStringList.Create;
    try
      ReadSection(Section, KeyList);
      Strings.BeginUpdate;
      try
        Strings.Clear;
        for I := 0 to KeyList.Count - 1 do
          Strings.Add(KeyList[I] + '=' + ReadString(Section, KeyList[I], ''))
      finally
        Strings.EndUpdate;
      end;
    finally
      KeyList.Free;
    end;
  end; {if}
end;

procedure TTntIniFile.EraseSection(const Section: WideString);
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.EraseSection(Section);
  end
  else begin
    { Windows NT/2000/XP and later }
    WideCheckFileExists;
    if not WritePrivateProfileStringW(PWideChar(Section), nil, nil,
      PWideChar(FFileName)) then
      raise EIniFileException.CreateResFmt(@SIniFileWriteError, [FileName]);
  end; {if}
end;

procedure TTntIniFile.DeleteKey(const Section, Ident: WideString);
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.DeleteKey(Section, Ident);
  end
  else begin
    { Windows NT/2000/XP and later }
    WideCheckFileExists;
    WritePrivateProfileStringW(PWideChar(Section), PWideChar(Ident), nil,
      PWideChar(FFileName));
  end; {if}
end;

procedure TTntIniFile.UpdateFile;
begin
  if (not Win32PlatformIsUnicode) then
  begin
    { Windows 95/98/Me }
    FAnsiIniFile.UpdateFile
  end
  else begin
    { Windows NT/2000/XP and later }
    WideCheckFileExists;
    WritePrivateProfileStringW(nil, nil, nil, PWideChar(FFileName));
  end; {if}
end;

procedure TTntIniFile.WideCheckFileExists;
var
  hFile, NumberOfBytesWritten: Cardinal;
  BOM: WideChar;
begin               
  if WideFileExists(FileName) then
    Exit;

  hFile := Tnt_CreateFileW(PWideChar(FileName), GENERIC_WRITE, 0,
                   nil, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
  if hFile <> 0 then
  try
    BOM := UNICODE_BOM;
    WriteFile(hFile, BOM, SizeOf(WideChar), NumberOfBytesWritten, nil);
  finally
    CloseHandle(hFile);
  end;
end;

{$ELSE}

destructor TTntIniFile.Destroy;
begin
  UpdateFile;
  inherited Destroy;
end;

{$ENDIF}




end.
